<template>
  <el-dialog :title="title" :visible.sync="show" :close-on-click-modal="false" :show-close="false" fullscreen append-to-body>
    <div v-if="!readonly" class="plane-tool-bar" :class="{ disabled: loadImgErr || drawing }">
      <el-row class="action-icons" type="flex" align="middle">
        <el-col class="item" align="middle" @click.native="activateDrawMode">
          <el-popover placement="top" trigger="manual" :value="visibleCircleAction" width="300">
            <div slot="reference" :style="{ color: actionState === 1 ? '#d23930' : '' }">
              <i class="fa fa-doodle" />
              绘制
            </div>
            <el-row type="flex" style="height: 25px" :class="{ disabled: drawing }">
              <el-col :span="4" class="circle-block-wrapper">
                <div class="circle-block" @click="penPointSize = 5">
                  <div class="circle" :class="{ 'active-circle': penPointSize === 5 }" style="width: 5px; height: 5px" />
                </div>
              </el-col>
              <el-col :span="4" class="circle-block-wrapper">
                <div class="circle-block" @click="penPointSize = 10">
                  <div class="circle" :class="{ 'active-circle': penPointSize === 10 }" style="width: 10px; height: 10px" />
                </div>
              </el-col>
              <el-col :span="4" class="circle-block-wrapper">
                <div class="circle-block" @click="penPointSize = 15">
                  <div class="circle" :class="{ 'active-circle': penPointSize === 15 }" style="width: 15px; height: 15px" />
                </div>
              </el-col>
              <el-col :span="4" class="circle-block-wrapper">
                <div class="circle-block" @click="penPointSize = 20">
                  <div class="circle" :class="{ 'active-circle': penPointSize === 20 }" style="width: 20px; height: 20px" />
                </div>
              </el-col>
              <el-col :span="4" class="circle-block-wrapper">
                <div class="circle-block">
                  <div class="circle" :class="{ 'active-circle': penPointSize === 25 }" style="width: 25px; height: 25px" @click="penPointSize = 25" />
                </div>
              </el-col>
              <el-col :span="4" class="circle-block-wrapper">
                <i class="el-icon-close pointer" @click="actionState = -1" />
              </el-col>
            </el-row>
          </el-popover>
        </el-col>
        <el-col class="item" align="middle" @click.native="stepBackward">
          <i class="fa fa-undo" :style="{ color: lines.length === 0 ? '#ccc' : '' }" />
          撤回
        </el-col>
        <el-col class="item" align="middle" @click.native="stepForward">
          <i class="fa fa-redo" :style="{ color: undoLines.length === 0 ? '#ccc' : '' }" />
          恢复
        </el-col>
        <el-col class="item" align="middle" @click.native="clearDrawPlaneHandle">
          <i class="fa fa-eraser" :style="{ color: lines.length === 0 && undoLines.length === 0 ? '#ccc' : '' }" />
          清空
        </el-col>
        <el-col class="item" align="middle">
          <el-popover placement="bottom" width="250" trigger="hover">
            <div slot="reference">
              <i class="icon el-icon-question" style="font-size: 20px" />
              帮助
            </div>
            <div class="hint-wrapper">
              <div class="hint-content">涂抹需选择的范围</div>
              <el-image :src="hintImage2" style="margin-bottom: 40px" />
            </div>
          </el-popover>
        </el-col>
        <el-col class="item disabled drawing" align="middle">
          <span v-show="drawing">绘图中不能操作...</span>
        </el-col>
      </el-row>
    </div>
    <div class="plane-draw-container" ref="canvasContainer">
      <canvas class="canvas-class" ref="canvas" @mousedown="mousedown($event)" @mouseup="mouseup($event)" @mousemove="mousemove($event)" />
      <el-empty class="load-img-err" description="加载图片失败" v-if="loadImgErr" />
    </div>
    <el-row v-if="backgroundImages.length > 1" type="flex" align="middle" class="carousel-tool-bar" :class="{ disabled: drawing }">
      <el-col align="middle" :span="4">
        <span class="pointer" v-show="imageIndex > 0" @click="prevPicture">
          <i class="el-icon-arrow-left" />
          上一张
        </span>
      </el-col>
      <el-col align="middle" :span="16">
        <span>{{ imageIndex + 1 }} / {{ backgroundImages.length }}</span>
      </el-col>
      <el-col align="middle" :span="4">
        <span class="pointer" v-show="backgroundImages.length > imageIndex + 1" @click="nextPicture">
          下一张
          <i class="el-icon-arrow-right" />
        </span>
      </el-col>
    </el-row>
    <div slot="footer" :class="{ disabled: drawing }">
      <el-button type="primary" :disabled="loadImgErr" @click="confirm" v-if="!readonly">完 成</el-button>
      <el-button @click="cancel">取 消</el-button>
    </div>
  </el-dialog>
</template>
<script>
// 画线涂鸦
import Hammer from 'hammerjs'
const reqAnimationFrame = (function () {
  return (
    window[Hammer.prefixed(window, 'requestAnimationFrame')] ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60)
    }
  )
})()
export default {
  name: 'LineDrawPlane',
  props: {
    title: { type: String, default: '选择范围' },
    show: { type: Boolean, default: false },
    drawAreas: { type: Array, default: () => {} },
    backgroundImages: { type: Array, default: () => {} },
    currentImage: { type: [Number, String], default: 0 },
    readonly: { type: Boolean, default: false }
  },
  data() {
    return {
      oCanvas: null,
      ctx: null,
      // 笔头大小，默认为10
      penPointSize: 5,
      // 面板最大宽度
      containerWidth: 0,
      // 面板最大高度
      containerHeight: 0,
      // 绘制进行中
      drawing: false,
      // 当前绘制线段
      activeLine: {},
      // 线段数组
      lines: [],
      // 默认线段颜色
      lineColor: 'red',
      // 回退的线段
      undoLines: [],
      backgroundImg: {},
      // 操作状态， 默认-1无操作， 1为绘制，2为拖拽，3为缩放
      actionState: -1,
      hammer: undefined,
      // 放大系数， 默认为1
      zoom: 1,
      lastZoom: 0,
      // 最大放大系数
      maxZoom: 10,
      // 偏移量
      offset: [0, 0],
      // 上次偏移量
      lastOffset: [0, 0],
      // 初始中心点
      originalPoint: [],
      ticking: false,
      imageIndex: 0,
      hintImage1: require('@/assets/icons/draw/drawLineHint1.png'),
      hintImage2: require('@/assets/icons/draw/drawLineHint2.png'),
      loadImgErr: false
    }
  },
  computed: {
    visibleCircleAction() {
      return this.actionState === 1
    },
    currImgURL() {
      const fileKey = this.backgroundImages[this.imageIndex]
      if (!fileKey) return ''
      return `${process.env.VUE_APP_BASE_API}/file/download?fileKey=${fileKey}&EPID=jffc_zhgc`
    }
  },
  watch: {
    show(val) {
      if (val) {
        this.drawing = false
        this.loadImgErr = false
        this.lines = this.drawAreas.map(item => {
          return { ...item }
        })
        if (this.currentImage > 0) {
          const imageIndex = this.backgroundImages.findIndex(item => item === this.currentImage)
          this.imageIndex = imageIndex > -1 ? imageIndex : 0
        } else {
          this.imageIndex = 0
        }
        this.$nextTick(() => {
          this.oCanvas = this.$refs.canvas
          this.ctx = this.oCanvas.getContext('2d')

          const container = this.$refs.canvasContainer
          this.containerWidth = container.offsetWidth
          this.containerHeight = container.offsetHeight
          this.clearDrawPlane(false)
          if (!this.hammer) {
            const mc = new Hammer.Manager(this.$refs.canvas)
            // 用管理器 可以同时触发旋转 拖拽 移动
            mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }))
            mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get('pan')])
            mc.on('panstart panmove panend', this.onPan)
            mc.on('pinchstart pinchmove pinchend', this.onPinch)
            this.hammer = mc
            if (this.backgroundImages.length > 0) {
              this.backgroundInit(this.currImgURL)
            }
          } else {
            if (this.backgroundImages.length > 0) {
              this.backgroundInit(this.currImgURL)
            }
          }
        })
      }
    }
  },
  methods: {
    // 返回上一页
    backward() {
      this.$emit('update:show', false)
    },
    clearDrawPlaneHandle() {
      if (this.lines.length === 0 && this.undoLines.length === 0) {
        return
      }
      this.lines = []
      this.undoLines = []
      this.clearDrawPlane()
    },
    /**
     * 清空画布
     */
    clearDrawPlane(flag = true) {
      const canvas = this.$refs.canvas
      const ctx = canvas.getContext('2d')
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      const img = this.backgroundImg
      if (flag && img) {
        this.repaintBackground(ctx, img)
      }
      this.actionState = -1
      this.drawing = false
    },
    activateDrawMode() {
      this.actionState = this.actionState === 1 ? -1 : 1
    },
    /**
     * 选择笔头大小
     * @param size 大小
     */
    selectPenPointSize(size) {
      if (this.penPointSize === size) {
        this.actionState = -1
        this.penPointSize = 0
      } else {
        this.actionState = 1
        this.penPointSize = size
      }
    },
    // 操作回退
    stepBackward() {
      if (this.lines.length === 0) return

      const canvas = this.$refs.canvas
      const ctx = canvas.getContext('2d')
      if (this.lines.length > 0) {
        this.undoLines.push(this.lines.pop())
      }
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      this.repaintBackground(ctx, this.backgroundImg)
      this.repaintLines(ctx, this.lines)
    },
    // 操作前进
    stepForward() {
      if (this.undoLines.length === 0) return

      const canvas = this.$refs.canvas
      const ctx = canvas.getContext('2d')
      if (this.undoLines.length > 0) {
        this.lines.push(this.undoLines.pop())
      }
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      this.repaintBackground(ctx, this.backgroundImg)
      this.repaintLines(ctx, this.lines)
    },
    // 取消绘制
    async cancel() {
      if (this.lines.length !== this.drawAreas.length) {
        const res1 = await this.$confirm('你尚未对数据进行保存，是否保存？', '提示', {
          type: 'warning',
          confirmButtonText: '保存',
          cancelButtonText: '直接返回'
        }).catch(() => false)
        if (res1 !== 'confirm') {
          this.clearDrawPlane(false)
          this.$emit('update:show', false)
          this.$emit('cancel')
          return
        }
        this.confirm()
      } else {
        this.clearDrawPlane(false)
        this.$emit('update:show', false)
        this.$emit('cancel')
      }
    },
    // 确认保存
    confirm() {
      const data = this.lines
      this.clearDrawPlane()
      this.$emit('update:currentImage', this.backgroundImages[this.imageIndex])
      this.$emit('update:drawAreas', data)
      // this.$emit('update:show', false)
      this.$emit('confirm', data)
    },
    /**
     * 背景初始化
     * @param imgUrl 图片路径
     */
    backgroundInit(imgUrl) {
      const that = this
      const img = new Image()
      that.backgroundImg = undefined
      const loading = this.$loading({ lock: true, text: '加载图片', spinner: 'el-icon-loading', background: 'rgba(255, 255, 255, 0.2)' })

      img.onload = () => {
        const canvas = that.$refs.canvas
        const imgW = img.width
        const imgH = img.height
        const ctx = canvas.getContext('2d')

        const scaling = this.getImgScaling(imgW, imgH, that.containerWidth, that.containerHeight)
        const realWidth = imgW / scaling
        const realHeight = imgH / scaling
        this.originalPoint = [realWidth / 2, realHeight / 2]
        canvas.width = realWidth
        canvas.height = realHeight
        ctx.drawImage(img, 0, 0, realWidth, realHeight)
        canvas.style.width = imgW / scaling + 'px'
        canvas.style.height = imgH / scaling + 'px'
        that.backgroundImg = img
        if (this.lines.length > 0) {
          this.repaintLines(ctx, this.lines)
        }
        loading.close()
      }
      img.onerror = () => {
        this.loadImgErr = true
        this.$message.error('加载图片失败，或图片已不存在')
        loading.close()
      }
      img.src = imgUrl
    },
    /**
     * 获取图片压缩比例
     * @param imgW 图片宽度
     * @param imgH 图片高度
     * @param containerW 容器宽度
     * @param containerH 容器高度
     */
    getImgScaling(imgW, imgH, containerW, containerH) {
      const scalingW = imgW / containerW
      const scalingH = imgH / containerH
      return scalingW > scalingH ? scalingW : scalingH
    },
    // 上一张图
    async prevPicture() {
      if (this.lines.length > 0) {
        const res1 = await this.$confirm('当前图片存在绘制线条，是否清空进入下一张', '提示', { type: 'warning' }).catch(() => false)
        if (res1 !== 'confirm') return

        this.imageIndex = this.imageIndex - 1
        this.lines = []
        this.undoLines = []
        this.clearDrawPlane(false)
        this.backgroundInit(this.currImgURL)
      } else {
        this.imageIndex = this.imageIndex - 1
        this.clearDrawPlane(false)
        this.backgroundInit(this.currImgURL)
      }
    },
    // 下一张图
    async nextPicture() {
      if (this.lines.length > 0) {
        const res1 = await this.$confirm('当前图片存在绘制线条，是否清空进入下一张', '提示', { type: 'warning' }).catch(() => false)
        if (res1 !== 'confirm') return

        this.imageIndex = this.imageIndex + 1
        this.lines = []
        this.undoLines = []
        this.clearDrawPlane(false)
        this.backgroundInit(this.currImgURL)
      } else {
        this.imageIndex = this.imageIndex + 1
        this.clearDrawPlane(false)
        this.backgroundInit(this.currImgURL)
      }
    },
    mousedown(e) {
      if (this.actionState !== 1) return

      this.drawing = true
      const rect = this.oCanvas.getBoundingClientRect()
      const pointX = ((e.clientX - rect.left) * this.oCanvas.width) / rect.width
      const pointY = ((e.clientY - rect.top) * this.oCanvas.height) / rect.height
      const pointSize = this.penPointSize / this.zoom
      this.activeLine = {
        color: this.lineColor,
        pointSize: pointSize,
        points: [[pointX / rect.width, pointY / rect.height]]
      }
      this.drawLineBegin(this.ctx, pointX / this.zoom, pointY / this.zoom, { pointSize })
    },
    mouseup(e) {
      if (this.actionState !== 1) return

      if (this.activeLine.points?.length > 1) {
        this.lines.push(this.activeLine)
        this.undoLines = []
      }
      this.activeLine = {}
      this.drawing = false
    },
    mousemove(e) {
      if (this.actionState === 1 && this.drawing) {
        const rect = this.oCanvas.getBoundingClientRect()
        const pointX = ((e.clientX - rect.left) * this.oCanvas.width) / rect.width
        const pointY = ((e.clientY - rect.top) * this.oCanvas.height) / rect.height
        this.activeLine.points.push([pointX / rect.width, pointY / rect.height])
        this.drawLineAction(this.ctx, pointX / this.zoom, pointY / this.zoom)
      }
    },
    /**
     * 重新绘制背景
     * @param ctx 画布对象
     * @param img
     */
    repaintBackground(ctx, img) {
      if (img) {
        ctx.drawImage(img, 0, 0, ctx.canvas.offsetWidth, ctx.canvas.offsetHeight)
      }
    },
    /**
     * 重新绘制线段
     * @param ctx 画布对象
     * @param lines 线段数组数据
     */
    repaintLines(ctx, lines = []) {
      const canvasW = ctx.canvas.clientWidth
      const canvasH = ctx.canvas.clientHeight
      if (lines.length === 0) {
        return
      }
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i]
        const points = line.points || []
        const firstPoint = points[0]

        ctx.beginPath()
        ctx.strokeStyle = line.color || this.lineColor
        ctx.lineWidth = line.pointSize || this.penPointSize
        ctx.lineCap = 'round'
        ctx.lineJoin = 'round'
        ctx.moveTo(firstPoint[0] * canvasW, firstPoint[1] * canvasH)
        for (let i = 1; i < points.length; i++) {
          const point = points[i]
          ctx.lineTo(point[0] * canvasW, point[1] * canvasH)
        }
        ctx.stroke()
      }
    },
    /**
     * 画线开始
     * @param ctx
     * @param x
     * @param y
     * @param color
     * @param pointSize
     */
    drawLineBegin(ctx, x, y, { color = this.lineColor, pointSize = this.penPointSize }) {
      ctx.beginPath()
      ctx.strokeStyle = color
      ctx.lineWidth = pointSize
      ctx.lineCap = 'round'
      ctx.lineJoin = 'round'
      ctx.moveTo(x, y)
    },
    /**
     * 画线移动
     * @param ctx
     * @param x
     * @param y
     */
    drawLineAction(ctx, x, y) {
      ctx.lineTo(x, y)
      ctx.stroke()
    },
    onPan(e) {
      const canvas = this.$refs.canvas
      if (this.zoom <= 1 || ![-1, 2].includes(this.actionState)) {
        return
      }
      const deltaX = e.deltaX
      const deltaY = e.deltaY
      if (e.type === 'panstart') {
        this.actionState = 2
        this.lastOffset[0] = this.offset[0]
        this.lastOffset[1] = this.offset[1]
      } else if (e.type === 'panmove') {
        this.offset[0] = this.lastOffset[0] + deltaX
        this.offset[1] = this.lastOffset[1] + deltaY
      } else if (e.type === 'panend') {
        if (this.originalPoint[0] - this.lastOffset[0] - canvas.getBoundingClientRect().width / 2 > deltaX) {
          this.offset[0] = this.originalPoint[0] - canvas.getBoundingClientRect().width / 2
        }
        if (deltaX > canvas.getBoundingClientRect().width / 2 - this.originalPoint[0] - this.lastOffset[0]) {
          this.offset[0] = canvas.getBoundingClientRect().width / 2 - this.originalPoint[0]
        }
        if (this.originalPoint[1] - this.lastOffset[1] - canvas.getBoundingClientRect().height / 2 > deltaY) {
          this.offset[1] = this.originalPoint[1] - canvas.getBoundingClientRect().height / 2
        }
        if (deltaY > canvas.getBoundingClientRect().height / 2 - this.originalPoint[1] - this.lastOffset[1]) {
          this.offset[1] = canvas.getBoundingClientRect().height / 2 - this.originalPoint[1]
        }
        this.actionState = -1
      }
      this.requestElementUpdate()
    },
    onPinch(e) {
      if (![-1, 3].includes(this.actionState) || (this.zoom >= this.maxZoom && e.scale > 1) || (this.zoom <= 1 && e.scale < 1)) {
        return
      }
      if (e.type === 'pinchstart') {
        this.actionState = 3
        this.lastZoom = this.zoom
        this.lastOffset[0] = this.offset[0]
        this.lastOffset[1] = this.offset[1]
      }
      if (e.type === 'pinchmove') {
        // this.zoom = Math.floor(this.lastZoom * e.scale * 100) / 100
        this.zoom = this.lastZoom * e.scale
        if (this.zoom >= this.maxZoom) {
          this.zoom = this.maxZoom
        }
        if (this.zoom < 1) {
          this.zoom = 1
        }
        this.offset[0] = (1 - e.scale) * (e.center.x - this.originalPoint[0] - this.lastOffset[0]) + this.lastOffset[0]
        this.offset[1] = (1 - e.scale) * (e.center.y - this.originalPoint[1] - this.lastOffset[1]) + this.lastOffset[1]
      }
      if (e.type === 'pinchend') {
        this.actionState = -1
      }
      this.requestElementUpdate()
    },
    // 渲染画布
    rendererCanvas() {
      const canvas = this.$refs.canvas
      // const maxZoom = this.maxZoom
      const zoom = Math.fround(this.zoom)
      const offset = this.zoom > 1 ? this.offset : [0, 0]
      const value = `translate3d(${offset[0]}px, ${offset[1]}px, 0) scale(${zoom}, ${zoom})`
      canvas.style.transition = 1.6
      canvas.style.webkitTransform = value /* 为Chrome/Safari */
      canvas.style.mozTransform = value /* 为Firefox */
      canvas.style.transform = value /* IE Opera? */
      this.ticking = false
    },
    requestElementUpdate() {
      if (!this.ticking) {
        reqAnimationFrame(this.rendererCanvas)
        this.ticking = true
      }
    }
  }
}
</script>
<style lang="scss" scoped>
.disabled {
  pointer-events: none;
}
.plane-tool-bar {
  font-size: 24px;
  margin-bottom: 10px;
}
.action-icons {
  min-width: 500px;
  max-width: 700px;
  .item {
    height: 30px;
    line-height: 30px;
    cursor: pointer;
    font-size: 14px;
    &:hover {
      background-color: #f5f7f9;
    }
  }
  .fa,
  .icon {
    font-size: 24px;
    vertical-align: middle;
  }
  .drawing {
    width: 120px;
    flex-shrink: 0;
  }
}
.circle-block-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  .circle-block {
    width: 25px;
    height: 25px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    .circle {
      background-color: #ccc;
      border-radius: 50%;
    }
    .active-circle {
      background-color: #d23931;
    }
  }
}
.hint-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #23272a;
  padding: 20px;
  border-radius: 10px;
  .hint-content {
    color: #fff;
    border-radius: 20px;
    background-color: #f34336;
    padding: 5px 20px;
    font-size: 14px;
    height: 35px;
    line-height: 24px;
    margin-bottom: 20px;
  }
}
.plane-draw-container {
  height: calc(100vh - 250px);
  width: 100%;
  background: #f5f7f9;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  position: relative;

  .canvas-class {
    width: 100%;
    //background-color: #FFF;
  }
  .load-img-err {
    position: absolute;
    z-index: 1;
  }
}
.carousel-tool-bar {
  height: 20px;
  margin-top: 10px;
  margin-bottom: -10px;
}
</style>
